import axios from 'axios'
import { ElMessage } from 'element-plus'
import type { AxiosRequestConfig, AxiosResponse, AxiosInstance } from 'axios'
import { serverConf } from '@/config/index'
import { getToken } from '.'

// 开发中应逐步补充
type HttpResCode =
  | 200 // success
  | 201 // success
  | 304 // success
  | 301 // success
  | 404 //
  | 403 //
  | 402 //
  | 401 //

export type HttpResRaw<D = unknown> = {
  msg: string
  data: D
  code: HttpResCode
}

export type HttpRes<R, C = HttpResCode> = [err: null, res: R, code: C] | [err: string, res: R | null, code: C] // 兼容 code 非 200 时 data 可用，实属接口不规范

export interface HttpInstance extends AxiosInstance {
  request<T, R = HttpRes<T>, D = unknown> (config: AxiosRequestConfig<D>): Promise<R>
  get<T, R = HttpRes<T>, D = unknown> (url: string, config?: AxiosRequestConfig<D>): Promise<R>
  delete<T, R = HttpRes<T>, D = unknown> (url: string, config?: AxiosRequestConfig<D>): Promise<R>
  head<T, R = HttpRes<T>, D = unknown> (url: string, config?: AxiosRequestConfig<D>): Promise<R>
  options<T, R = HttpRes<T>, D = unknown> (url: string, config?: AxiosRequestConfig<D>): Promise<R>
  post<T, R = HttpRes<T>, D = unknown> (url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>
  put<T, R = HttpRes<T>, D = unknown> (url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>
  patch<T, R = HttpRes<T>, D = unknown> (url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>
  fork: typeof createHttp
}

function createHttp (): HttpInstance {
  const http = axios.create({
    baseURL: serverConf?.BASEURL,
    timeout: 5000,
  })

  http.interceptors.request.use(setRequestCredentials)

  http.interceptors.response.use(printResponseWarning)

  // 此拦截器会改变响应体，拦截后仅返回接口数据
  // 因此要在最后注册！
  http.interceptors.response.use(transformResponse as any, HandleError)

  return http as HttpInstance
}


/**
 * 请求拦截器。
 * 在请求发出时，设置凭证信息，如 token
 */
function setRequestCredentials (config: AxiosRequestConfig) {
  const token = getToken()

  if (!config.headers) {
    config.headers = {}
  }

  if (token) {
    config.headers['IM-TOKEN'] = token
  }

  return config
}


/**
 * 响应拦截器。
 * 当请求的响应值存在风险时，在控制台打印出数据明细，
 * 以方便开发者调试，同时弹窗显示返回的错误消息。
 */
function printResponseWarning (response: AxiosResponse): AxiosResponse {
  const { method, url, headers } = response.config
  const { code, msg, data } = resolveData(response)
  const SUCCESS = Number(code) === 200
  const request = response.config

  // 错误警告 或者 data 为 null 警告
  if (!SUCCESS || SUCCESS && data == null) {
    warn(code, msg || 'No Data!', method, url, request, data)
  }

  // 错误弹窗，可通过特定的 header 配置禁用
  if (!SUCCESS && !headers?.['x-suppress-warning']) {
    ElMessage.warning(msg)
  }
  return response
}


/**
 * 转换响应值为 `error first` 的形式
 */
function transformResponse (response: AxiosResponse<HttpResRaw>): HttpRes<unknown> {
  const { code, msg, data } = resolveData(response)
  const error = code < 200 || code >= 300 ? msg : null
  return [error, data, code as HttpResCode]
}

/**
 *
 * 接口异常http status非200错误处理
 * @returns
 */
function HandleError (err: any) {
  console.log('err=>>>>>>', err)
  const { status, data } = err.response
  const { url, method } = err.config
  ElMessage.error(err.message)
  warn(status, err.message, method, url, err.config, data)
  return transformResponse(err.response)
}

/**
 * 解析 axios response.
 * 返回 `msg` `code` `data` 字段
 */
function resolveData (response: AxiosResponse<HttpResRaw>) {
  if (response.data instanceof Blob || typeof response.data === 'string') {
    return {
      code: response.status,
      msg: response.statusText,
      data: response.data,
    }
  }

  // hack: 兼容不规范的状态码
  if (typeof response.data.code === 'string') {
    response.data.code = Number(response.data.code) as HttpResCode
    console.error('后端接口返回状态码不规范！code 是字符串' + `'${response.data.code}'`)
  }

  return response.data
}


/**
 * 控制台输出警告
 */
function warn (code: number, msg: string, method = '', url = '', req: unknown, res: unknown) {
  console.group(`%c ${code} %c ${msg} `, 'color:yellow; background: black', 'background: yellow; color: red')
  console.log(`%c ${method?.toUpperCase()} %c ${url} `, 'background: green; color: white', 'background: #eeecda')
  console.log('%c req ', 'background: grey; color: white', req)
  console.log('%c res ', 'background: black; color: white', res)
  console.groupEnd()
}

export const http = createHttp()
